/**
 * Canvas 工具函数 - 统一管理画布配置和高DPI支持
 */

/**
 * 将中文几何术语转换为数学符号
 * @param {string} text - 输入文本
 * @param {boolean} enableConversion - 是否启用转换（默认true）
 * @returns {string} 转换后的文本
 */
export function convertToMathSymbols(text, enableConversion = true) {
  if (!text || !enableConversion) return text;
  
  return text
    .replace(/三角形/g, '△')
    .replace(/角/g, '∠');
}

/**
 * 设置高DPI画布，确保在高分辨率屏幕上清晰显示
 * @param {HTMLCanvasElement} canvas - Canvas元素
 * @param {number} width - 逻辑宽度
 * @param {number} height - 逻辑高度
 * @returns {CanvasRenderingContext2D} - 2D渲染上下文
 */
export function setupHighDPICanvas(canvas, width, height) {
  const rawDpr = window.devicePixelRatio || 1;
  
  // 移动端性能优化：限制最大 DPR
  // 在移动端，过高的 DPR 会导致严重的性能问题
  // 例如 iPhone 的 DPR=3，iPad Pro 的 DPR=2
  // 限制为最大 2，在清晰度和性能之间取得平衡
  const isMobile = window.innerWidth <= 768;
  const dpr = isMobile ? Math.min(rawDpr, 2) : Math.min(rawDpr, 2);
  
  // 设置Canvas的实际像素尺寸（考虑设备像素比）
  canvas.width = width * dpr;
  canvas.height = height * dpr;
  
  // 设置Canvas的CSS显示尺寸
  canvas.style.width = `${width}px`;
  canvas.style.height = `${height}px`;
  
  // 获取2D上下文并缩放，确保绘制清晰
  const ctx = canvas.getContext('2d');
  ctx.scale(dpr, dpr);
  
  return ctx;
}

/**
 * 获取响应式画布尺寸
 * @returns {{width: number, height: number}}
 */
export function getResponsiveCanvasSize() {
  const viewportWidth = window.innerWidth;
  const viewportHeight = window.innerHeight;
  
  let width, height;
  
  // 小屏幕（<480px）需要旋转
  if (viewportWidth <= 480) {
    width = viewportHeight;
    height = viewportWidth;
  } 
  // 移动端（480-768px）
  else if (viewportWidth <= 768) {
    width = viewportWidth;
    height = viewportHeight;
  }
  // 桌面端
  else {
    width = Math.max(1400, viewportWidth);
    height = Math.max(600, viewportHeight);
  }
  
  return { width, height };
}

/**
 * 统一的画布布局计算
 * @param {number} canvasWidth - 画布宽度
 * @param {number} canvasHeight - 画布高度
 * @returns {Object} 布局常量
 */
export function getCanvasLayout(canvasWidth, canvasHeight) {
  return {
    width: canvasWidth,
    height: canvasHeight,
    LEFT_PANEL_WIDTH: Math.max(180, canvasWidth * 0.14),
    RIGHT_PANEL_WIDTH: Math.max(150, canvasWidth * 0.12),
    TITLE_HEIGHT: Math.max(60, canvasHeight * 0.1),
    get CANVAS_AREA_X() {
      return this.LEFT_PANEL_WIDTH + 20;
    },
    get CANVAS_AREA_WIDTH() {
      return this.width - this.LEFT_PANEL_WIDTH - this.RIGHT_PANEL_WIDTH - 40;
    }
  };
}

/**
 * 获取画布坐标（支持触摸和鼠标事件，支持小屏幕旋转）
 * @param {Event} e - 事件对象
 * @param {HTMLCanvasElement} canvas - Canvas元素
 * @returns {{x: number, y: number}} 画布坐标
 */
export function getCanvasCoordinates(e, canvas) {
  const rect = canvas.getBoundingClientRect();
  
  // 支持触摸和鼠标事件
  const clientX = e.touches ? e.touches[0].clientX : e.clientX;
  const clientY = e.touches ? e.touches[0].clientY : e.clientY;
  
  // 获取逻辑尺寸（绘制坐标空间）
  // 通过 CSS 尺寸获取，避免 DPR 计算问题
  const logicalWidth = parseFloat(canvas.style.width) || rect.width;
  const logicalHeight = parseFloat(canvas.style.height) || rect.height;
  
  // 获取相对于 canvas 的屏幕坐标（0 到 rect.width/height）
  const relX = clientX - rect.left;
  const relY = clientY - rect.top;
  
  let x, y;
  
  // 检查是否是小屏幕旋转模式
  if (window.innerWidth <= 480) {
    // 旋转90度：屏幕Y -> 画布X，屏幕X -> 画布Y（倒序）
    // 映射到逻辑坐标空间
    x = (relY / rect.height) * logicalWidth;
    y = ((rect.width - relX) / rect.width) * logicalHeight;
  } else {
    // 正常模式：直接映射
    x = (relX / rect.width) * logicalWidth;
    y = (relY / rect.height) * logicalHeight;
  }
  
  return { x, y };
}

/**
 * 默认画布尺寸常量
 */
export const DEFAULT_CANVAS_SIZE = {
  width: 1400,
  height: 600
};

/**
 * 像素缩放因子（用于长度和面积计算）
 * 100像素 = 3单位
 */
export const SCALE = 0.03;

// ============ 几何计算工具函数 ============

/**
 * 计算两点之间的距离
 * @param {Object} p1 - 第一个点 {x, y}
 * @param {Object} p2 - 第二个点 {x, y}
 * @returns {number} 距离
 */
export function getDistance(p1, p2) {
  return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}

/**
 * 计算三点形成的角度（以第二个点为顶点）- 已四舍五入
 * @param {Object} p1 - 第一个点 {x, y}
 * @param {Object} vertex - 顶点 {x, y}
 * @param {Object} p3 - 第三个点 {x, y}
 * @returns {number} 角度（度数，已四舍五入）
 * @deprecated 使用 calculateAngleExact 获取精确值
 */
export function calculateAngle(p1, vertex, p3) {
  return Math.round(calculateAngleExact(p1, vertex, p3));
}

/**
 * 计算常量的实时值
 * @param {Object} constant - 常量对象 {type, pointIds, name, composedOf}
 * @param {Array} allPoints - 所有点的数组
 * @param {Array} allConstants - 所有常量的数组（用于计算组合面积）
 * @returns {string} 格式化的值
 */
export function calculateConstantValue(constant, allPoints, allConstants = []) {
  // 组合面积：由多个面积常量相加
  if (constant.type === 'area' && constant.composedOf && constant.composedOf.length > 0) {
    let totalArea = 0;
    for (const areaId of constant.composedOf) {
      const areaConstant = allConstants.find(c => c.id === areaId);
      if (areaConstant) {
        const areaValue = calculateConstantValue(areaConstant, allPoints, allConstants);
        const num = parseFloat(areaValue);
        if (!isNaN(num)) {
          totalArea += num;
        }
      }
    }
    // 保留两位小数
    return totalArea.toFixed(2);
  }
  
  // 基于点的常量
  if (!constant.pointIds || constant.pointIds.length === 0) return '-';
  
  const points = constant.pointIds.map(id => 
    allPoints?.find(p => p.id === id)
  ).filter(p => p !== undefined);
  
  if (points.length !== constant.pointIds.length) return '-';
  
  if (constant.type === 'angle' && points.length === 3) {
    // 计算角度（以第二个点为顶点）
    const [p1, p2, p3] = points;
    const angle = calculateAngleExact(p1, p2, p3);
    return Math.round(angle) + '°';
  } else if (constant.type === 'length' && points.length === 2) {
    // 计算长度（应用缩放）
    const [p1, p2] = points;
    const length = getDistance(p1, p2);
    // 保留两位小数
    return (length * SCALE).toFixed(2);
  } else if (constant.type === 'area' && points.length >= 3) {
    // 计算面积（鞋带公式，应用缩放的平方）
    let area = 0;
    for (let i = 0; i < points.length; i++) {
      const j = (i + 1) % points.length;
      area += points[i].x * points[j].y;
      area -= points[j].x * points[i].y;
    }
    // 保留两位小数
    return (Math.abs(area / 2) * SCALE * SCALE).toFixed(2);
  }
  return '-';
}

/**
 * 修正三角形内角和精度
 * @param {number} angleA - 角A
 * @param {number} angleB - 角B
 * @param {number} angleC - 角C
 * @returns {Array} 修正后的角度数组 [angleA, angleB, angleC]
 */
export function fixTriangleAngleSum(angleA, angleB, angleC) {
  const angleSum = angleA + angleB + angleC;
  
  // 如果误差超过 0.01°，按比例分配误差
  if (Math.abs(angleSum - 180) > 0.01) {
    const error = 180 - angleSum;
    angleA += error / 3;
    angleB += error / 3;
    angleC += error / 3;
  }
  
  // 四舍五入到整数
  return [Math.round(angleA), Math.round(angleB), Math.round(angleC)];
}

/**
 * 应用三角形角度约束：将三个角度四舍五入为整数，并确保和为180°
 * @param {number} a1 - 角度1（精确值）
 * @param {number} a2 - 角度2（精确值）
 * @param {number} a3 - 角度3（精确值）
 * @returns {Object} {angle1, angle2, angle3} - 约束后的整数角度
 */
export function applyTriangleAngleConstraint(a1, a2, a3) {
  // 四舍五入到整数
  let angle1 = Math.round(a1);
  let angle2 = Math.round(a2);
  let angle3 = Math.round(a3);
  
  // 计算总和
  const sum = angle1 + angle2 + angle3;
  const diff = 180 - sum;
  
  // 如果总和不等于180，调整角度
  if (diff !== 0) {
    // 找到四舍五入后差值最大的角度，优先调整它
    const diffs = [
      { idx: 0, diff: Math.abs(a1 - angle1), value: angle1 },
      { idx: 1, diff: Math.abs(a2 - angle2), value: angle2 },
      { idx: 2, diff: Math.abs(a3 - angle3), value: angle3 }
    ];
    
    // 按差值排序，优先调整差值最大的
    diffs.sort((a, b) => b.diff - a.diff);
    
    // 调整角度使总和等于180
    for (const item of diffs) {
      const newValue = item.value + diff;
      if (newValue >= 1 && newValue <= 178) {
        if (item.idx === 0) {
          angle1 = newValue;
        } else if (item.idx === 1) {
          angle2 = newValue;
        } else {
          angle3 = newValue;
        }
        break;
      }
    }
    
    // 确保所有角度都在合理范围内 [1, 178]
    angle1 = Math.max(1, Math.min(178, angle1));
    angle2 = Math.max(1, Math.min(178, angle2));
    angle3 = Math.max(1, Math.min(178, angle3));
    
    // 最终调整：确保总和为180
    const finalSum = angle1 + angle2 + angle3;
    const finalDiff = 180 - finalSum;
    
    if (finalDiff !== 0) {
      // 优先调整第一个角度
      if (angle1 + finalDiff >= 1 && angle1 + finalDiff <= 178) {
        angle1 += finalDiff;
      } else if (angle2 + finalDiff >= 1 && angle2 + finalDiff <= 178) {
        angle2 += finalDiff;
      } else if (angle3 + finalDiff >= 1 && angle3 + finalDiff <= 178) {
        angle3 += finalDiff;
      }
    }
  }
  
  return { angle1, angle2, angle3 };
}

/**
 * 计算角度（精确值，不四舍五入）
 * @param {Object} p1 - 第一个点 {x, y}
 * @param {Object} vertex - 顶点 {x, y}
 * @param {Object} p2 - 第二个点 {x, y}
 * @returns {number} 角度（度数，精确值）
 */
export function calculateAngleExact(p1, vertex, p2) {
  const v1 = { x: p1.x - vertex.x, y: p1.y - vertex.y };
  const v2 = { x: p2.x - vertex.x, y: p2.y - vertex.y };
  
  const dot = v1.x * v2.x + v1.y * v2.y;
  const len1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
  const len2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
  
  if (len1 === 0 || len2 === 0) return 0;
  
  const cosAngle = dot / (len1 * len2);
  const clampedCos = Math.max(-1, Math.min(1, cosAngle));
  const angle = Math.acos(clampedCos);
  return angle * 180 / Math.PI;
}

/**
 * 计算两条线段的交点
 * @param {Object} line1 - 第一条线段 {startPointId, endPointId}
 * @param {Object} line2 - 第二条线段 {startPointId, endPointId}
 * @param {Array} points - 所有点的数组
 * @returns {Object|null} 交点坐标 {x, y} 或 null（如果不相交）
 */
export function calculateLineIntersection(line1, line2, points) {
  const p1 = points.find(p => p.id === line1.startPointId);
  const p2 = points.find(p => p.id === line1.endPointId);
  const p3 = points.find(p => p.id === line2.startPointId);
  const p4 = points.find(p => p.id === line2.endPointId);
  
  if (!p1 || !p2 || !p3 || !p4) return null;
  
  const x1 = p1.x, y1 = p1.y;
  const x2 = p2.x, y2 = p2.y;
  const x3 = p3.x, y3 = p3.y;
  const x4 = p4.x, y4 = p4.y;
  
  const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
  
  // 平行或重合
  if (Math.abs(denom) < 0.0001) return null;
  
  const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
  const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
  
  // 检查交点是否在两条线段上
  if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
    return {
      x: x1 + t * (x2 - x1),
      y: y1 + t * (y2 - y1)
    };
  }
  
  return null;
}


/**
 * 更新所有交点位置（自动检测并创建/更新/删除交点）
 * @param {Array} points - 当前所有点的数组
 * @param {Array} lines - 当前所有线段的数组
 * @param {Array} parallels - 平行线数组（可选）
 * @param {Array} rays - 射线数组（可选）
 * @returns {Array} 更新后的点数组
 */
export function updateIntersectionPoints(points, lines, parallels = [], rays = []) {
  const updatedPoints = [...points];

  // 将平行线转换为虚拟线段（用于交点计算）
  const virtualLinesFromParallels = parallels.map(parallel => {
    const fromPoint = points.find(p => p.id === parallel.fromPointId);
    const lineStart = points.find(p => p.id === parallel.toLineStartId);
    const lineEnd = points.find(p => p.id === parallel.toLineEndId);

    if (!fromPoint || !lineStart || !lineEnd) return null;

    // 计算平行线的方向向量（与目标线段相同）
    const dx = lineEnd.x - lineStart.x;
    const dy = lineEnd.y - lineStart.y;
    const length = Math.sqrt(dx * dx + dy * dy);

    if (length === 0) return null;

    // 计算单位方向向量
    const unitDx = dx / length;
    const unitDy = dy / length;

    // 延伸到画布边界外（使用一个足够大的距离）
    const extensionDistance = 5000;

    // 创建虚拟的起点和终点
    const virtualStartPoint = {
      id: `${parallel.id}_virtual_start`,
      x: fromPoint.x - unitDx * extensionDistance,
      y: fromPoint.y - unitDy * extensionDistance
    };

    const virtualEndPoint = {
      id: `${parallel.id}_virtual_end`,
      x: fromPoint.x + unitDx * extensionDistance,
      y: fromPoint.y + unitDy * extensionDistance
    };

    // 创建虚拟线段
    return {
      id: parallel.id,
      startPointId: virtualStartPoint.id,
      endPointId: virtualEndPoint.id,
      _virtualPoints: [virtualStartPoint, virtualEndPoint],
      _isParallel: true
    };
  }).filter(Boolean);

  // 将射线转换为虚拟线段（用于交点计算）
  const virtualLinesFromRays = rays.map(ray => {
    const startPoint = points.find(p => p.id === ray.startPointId);
    const throughPoint = points.find(p => p.id === ray.throughPointId);

    if (!startPoint || !throughPoint) return null;

    // 计算射线的方向向量
    const dx = throughPoint.x - startPoint.x;
    const dy = throughPoint.y - startPoint.y;
    const length = Math.sqrt(dx * dx + dy * dy);

    if (length === 0) return null;

    // 计算单位方向向量
    const unitDx = dx / length;
    const unitDy = dy / length;

    // 延伸到画布边界外（使用一个足够大的距离）
    const extensionDistance = 5000;

    // 创建虚拟的终点（从起点出发，沿方向延伸）
    const virtualEndPoint = {
      id: `${ray.id}_virtual_end`,
      x: startPoint.x + unitDx * extensionDistance,
      y: startPoint.y + unitDy * extensionDistance
    };

    // 创建虚拟线段（从起点到虚拟终点）
    return {
      id: ray.id,
      startPointId: startPoint.id,
      endPointId: virtualEndPoint.id,
      _virtualPoints: [virtualEndPoint],
      _isRay: true
    };
  }).filter(Boolean);

  // 合并真实线段和虚拟线段
  const allLines = [...lines, ...virtualLinesFromParallels, ...virtualLinesFromRays];

  // 为虚拟线段创建临时点数组（使用 updatedPoints 而不是 points，这样包含了已有的交点）
  const allPoints = [...updatedPoints];
  virtualLinesFromParallels.forEach(vLine => {
    if (vLine._virtualPoints) {
      allPoints.push(...vLine._virtualPoints);
    }
  });
  virtualLinesFromRays.forEach(vLine => {
    if (vLine._virtualPoints) {
      allPoints.push(...vLine._virtualPoints);
    }
  });
  
  // 先更新现有的交点（保留所有属性包括 labelOffset）
  updatedPoints.forEach((point, index) => {
    if (point.isIntersection && point.intersectionLines) {
      const [id1, id2] = point.intersectionLines;

      // 查找对应的线段（包括虚拟线段）
      const obj1 = allLines.find(l => l.id === id1);
      const obj2 = allLines.find(l => l.id === id2);

      if (obj1 && obj2) {
        const intersection = calculateLineIntersection(obj1, obj2, allPoints);

        if (intersection) {
          // 使用展开运算符保留所有属性（包括 labelOffset）
          updatedPoints[index] = {
            ...point,
            x: intersection.x,
            y: intersection.y
          };
        }
      }
    }
  });
  
  // 辅助函数：添加交点
  const addIntersectionPoint = (intersection, id1, id2, type1 = 'line', type2 = 'line') => {
    if (!intersection) return;

    // 检查交点位置是否已经有其他点（距离小于5像素视为同一位置）
    const COLLISION_THRESHOLD = 5;
    const hasPointAtIntersection = updatedPoints.some(p => {
      const distance = Math.sqrt(
        Math.pow(p.x - intersection.x, 2) +
        Math.pow(p.y - intersection.y, 2)
      );
      return distance < COLLISION_THRESHOLD;
    });

    // 只有在交点位置没有其他点时才创建交点
    if (!hasPointAtIntersection) {
      const newPoint = {
        id: Date.now() + Math.random(),
        x: intersection.x,
        y: intersection.y,
        label: '',
        color: '#007AFF',
        draggable: false,
        isIntersection: true,
        intersectionLines: [id1, id2],
        intersectionTypes: [type1, type2] // 记录交点类型
      };
      updatedPoints.push(newPoint);
    }
  };

  // 检测线段与线段的交点
  for (let i = 0; i < allLines.length; i++) {
    for (let j = i + 1; j < allLines.length; j++) {
      const line1 = allLines[i];
      const line2 = allLines[j];

      // 检查是否已经存在这个交点
      const existingIntersection = updatedPoints.find(p =>
        p.isIntersection &&
        p.intersectionLines &&
        ((p.intersectionLines[0] === line1.id && p.intersectionLines[1] === line2.id) ||
         (p.intersectionLines[0] === line2.id && p.intersectionLines[1] === line1.id))
      );

      if (!existingIntersection) {
        const intersection = calculateLineIntersection(line1, line2, allPoints);
        addIntersectionPoint(intersection, line1.id, line2.id, 'line', 'line');
      }
    }
  }

  // 移除不再存在的交点（对应的线段被删除）
  return updatedPoints.filter(point => {
    if (!point.isIntersection) return true;
    if (!point.intersectionLines) return true;

    const [id1, id2] = point.intersectionLines;

    // 查找对应的线段（包括虚拟线段）
    const obj1 = allLines.find(l => l.id === id1);
    const obj2 = allLines.find(l => l.id === id2);

    if (!obj1 || !obj2) return false;

    // 检查是否还有交点
    const intersection = calculateLineIntersection(obj1, obj2, allPoints);

    return intersection !== null;
  });
}

/**
 * 查找点击的点
 * @param {number} x - 鼠标X坐标
 * @param {number} y - 鼠标Y坐标
 * @param {Array} points - 所有点的数组
 * @param {number} pointSize - 点的大小
 * @param {number} threshold - 点击阈值
 * @returns {Object|null} 点击的点对象或null
 */
export function findClickedPoint(x, y, points, pointSize = 8, threshold = 5) {
  for (const point of points) {
    const dist = Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2));
    if (dist < pointSize + threshold) {
      return point;
    }
  }
  return null;
}

/**
 * 查找点击的线段
 * @param {number} x - 鼠标X坐标
 * @param {number} y - 鼠标Y坐标
 * @param {Array} lines - 所有线段的数组
 * @param {Array} points - 所有点的数组
 * @param {number} threshold - 点击阈值
 * @returns {Object|null} 点击的线段信息 {type: 'line', item: line, projection: {x, y}, t: number} 或 null
 */
export function findClickedLine(x, y, lines, points, threshold = 8) {
  for (const line of lines) {
    const startPoint = points.find(p => p.id === line.startPointId);
    const endPoint = points.find(p => p.id === line.endPointId);
    
    if (!startPoint || !endPoint) continue;
    
    // 计算点到线段的距离
    const lineLength = getDistance(startPoint, endPoint);
    const distToStart = getDistance({ x, y }, startPoint);
    const distToEnd = getDistance({ x, y }, endPoint);
    
    // 使用三角形面积法计算点到线段的垂直距离
    const area = Math.abs(
      (endPoint.x - startPoint.x) * (startPoint.y - y) - 
      (startPoint.x - x) * (endPoint.y - startPoint.y)
    );
    const height = area / lineLength;
    
    // 检查点是否在线段附近
    if (height < threshold && distToStart < lineLength + threshold && distToEnd < lineLength + threshold) {
      // 计算点击位置在线段上的投影点
      const t = Math.max(0, Math.min(1, 
        ((x - startPoint.x) * (endPoint.x - startPoint.x) + 
         (y - startPoint.y) * (endPoint.y - startPoint.y)) / (lineLength * lineLength)
      ));
      
      const projectionX = startPoint.x + t * (endPoint.x - startPoint.x);
      const projectionY = startPoint.y + t * (endPoint.y - startPoint.y);
      
      return { 
        type: 'line', 
        item: line, 
        projection: { x: projectionX, y: projectionY },
        t: t // 在线段上的位置比例 (0-1)
      };
    }
  }
  
  return null;
}

/**
 * 查找点击的射线
 * @param {number} x - 点击的x坐标
 * @param {number} y - 点击的y坐标
 * @param {Array} rays - 所有射线的数组
 * @param {Array} points - 所有点的数组
 * @param {number} threshold - 点击阈值
 * @returns {Object|null} 点击的射线信息 {type: 'ray', item: ray} 或 null
 */
export function findClickedRay(x, y, rays, points, threshold = 8) {
  for (const ray of rays) {
    const startPoint = points.find(p => p.id === ray.startPointId);
    const throughPoint = points.find(p => p.id === ray.throughPointId);

    if (!startPoint || !throughPoint) continue;

    // 计算射线的方向向量
    const dx = throughPoint.x - startPoint.x;
    const dy = throughPoint.y - startPoint.y;
    const length = Math.sqrt(dx * dx + dy * dy);

    if (length === 0) continue;

    // 单位方向向量
    const unitDx = dx / length;
    const unitDy = dy / length;

    // 计算点击点相对于起点的向量
    const px = x - startPoint.x;
    const py = y - startPoint.y;

    // 计算点击点在射线方向上的投影长度
    const projectionLength = px * unitDx + py * unitDy;

    // 如果投影长度为负，说明点击点在射线起点的反方向，不在射线上
    if (projectionLength < 0) continue;

    // 计算投影点
    const projX = startPoint.x + projectionLength * unitDx;
    const projY = startPoint.y + projectionLength * unitDy;

    // 计算点击点到射线的垂直距离
    const distance = Math.sqrt((x - projX) * (x - projX) + (y - projY) * (y - projY));

    // 如果距离小于阈值，说明点击到了射线
    if (distance < threshold) {
      return {
        type: 'ray',
        item: ray
      };
    }
  }

  return null;
}

/**
 * 查找所有被点击的线段（用于处理重叠线段）
 * @param {number} x - 鼠标X坐标
 * @param {number} y - 鼠标Y坐标
 * @param {Array} lines - 所有线段的数组
 * @param {Array} points - 所有点的数组
 * @param {number} threshold - 点击阈值
 * @returns {Array} 所有点击的线段信息数组 [{type: 'line', item: line, projection: {x, y}, t: number}]
 */
export function findAllClickedLines(x, y, lines, points, threshold = 8) {
  const clickedLines = [];
  
  for (const line of lines) {
    const startPoint = points.find(p => p.id === line.startPointId);
    const endPoint = points.find(p => p.id === line.endPointId);
    
    if (!startPoint || !endPoint) continue;
    
    // 计算点到线段的距离
    const lineLength = getDistance(startPoint, endPoint);
    const distToStart = getDistance({ x, y }, startPoint);
    const distToEnd = getDistance({ x, y }, endPoint);
    
    // 使用三角形面积法计算点到线段的垂直距离
    const area = Math.abs(
      (endPoint.x - startPoint.x) * (startPoint.y - y) - 
      (startPoint.x - x) * (endPoint.y - startPoint.y)
    );
    const height = area / lineLength;
    
    // 检查点是否在线段附近
    if (height < threshold && distToStart < lineLength + threshold && distToEnd < lineLength + threshold) {
      // 计算点击位置在线段上的投影点
      const t = Math.max(0, Math.min(1, 
        ((x - startPoint.x) * (endPoint.x - startPoint.x) + 
         (y - startPoint.y) * (endPoint.y - startPoint.y)) / (lineLength * lineLength)
      ));
      
      const projectionX = startPoint.x + t * (endPoint.x - startPoint.x);
      const projectionY = startPoint.y + t * (endPoint.y - startPoint.y);
      
      clickedLines.push({ 
        type: 'line', 
        item: line, 
        projection: { x: projectionX, y: projectionY },
        t: t // 在线段上的位置比例 (0-1)
      });
    }
  }
  
  return clickedLines;
}

/**
 * 查找被点击的垂线
 * @param {number} x - 点击的x坐标
 * @param {number} y - 点击的y坐标
 * @param {Array} perpendiculars - 垂线数组
 * @param {Array} points - 点数组
 * @param {number} threshold - 点击阈值（像素）
 * @returns {Object|null} 返回 {type: 'perpendicular', item: perpendicular} 或 null
 */
export function findClickedPerpendicular(x, y, perpendiculars, points, threshold = 8) {
  for (const perp of perpendiculars) {
    const fromPoint = points.find(p => p.id === perp.fromPointId);
    const footPoint = points.find(p => p.id === perp.footPointId);
    
    if (!fromPoint || !footPoint) continue;
    
    const lineLength = getDistance(fromPoint, footPoint);
    if (lineLength === 0) continue;
    
    const distToStart = getDistance({ x, y }, fromPoint);
    const distToEnd = getDistance({ x, y }, footPoint);
    
    // 使用三角形面积法计算点到线段的垂直距离
    const area = Math.abs(
      (footPoint.x - fromPoint.x) * (fromPoint.y - y) - 
      (fromPoint.x - x) * (footPoint.y - fromPoint.y)
    );
    const height = area / lineLength;
    
    // 检查点是否在垂线附近
    if (height < threshold && distToStart < lineLength + threshold && distToEnd < lineLength + threshold) {
      return { 
        type: 'perpendicular', 
        item: perp
      };
    }
  }

  return null;
}


// ============ UI 绘制工具函数 ============

/**
 * 绘制画布背景
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {number} width - 宽度
 * @param {number} height - 高度
 */
export function drawBackground(ctx, width, height) {
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(0, 0, width, height);
}

/**
 * 绘制标题区域
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {Object} layout - 布局对象（包含 TITLE_HEIGHT）
 */
export function drawTitleBackground(ctx, layout) {
  ctx.fillStyle = '#f8f9fa';
  ctx.fillRect(0, 0, layout.width, layout.TITLE_HEIGHT);
}

/**
 * 绘制并截断过长的标题
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {string} title - 标题文本
 * @param {number} x - X坐标
 * @param {number} y - Y坐标
 * @param {number} maxWidth - 最大宽度
 * @param {string} font - 字体样式
 * @param {string} color - 文字颜色
 */
export function drawTitle(ctx, title, x, y, maxWidth, font = 'bold 22px Times New Roman', color = '#1d1d1f') {
  if (!title) return;
  
  ctx.fillStyle = color;
  ctx.font = font;
  
  // 处理标题过长的情况
  let displayTitle = title;
  if (ctx.measureText(title).width > maxWidth) {
    let truncated = title;
    while (ctx.measureText(truncated + '...').width > maxWidth && truncated.length > 0) {
      truncated = truncated.slice(0, -1);
    }
    displayTitle = truncated + '...';
  }
  
  ctx.fillText(displayTitle, x, y);
}

/**
 * 绘制圆角面板（带阴影）
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {number} x - X坐标
 * @param {number} y - Y坐标
 * @param {number} width - 宽度
 * @param {number} height - 高度
 * @param {number} radius - 圆角半径
 * @param {string} fillColor - 填充颜色
 * @param {boolean} withShadow - 是否添加阴影
 */
export function drawPanel(ctx, x, y, width, height, radius = 12, fillColor = '#ffffff', withShadow = true) {
  if (withShadow) {
    ctx.shadowColor = 'rgba(0,0,0,0.08)';
    ctx.shadowBlur = 10;
    ctx.shadowOffsetY = 2;
  }
  
  ctx.fillStyle = fillColor;
  ctx.beginPath();
  ctx.roundRect(x, y, width, height, radius);
  ctx.fill();
  
  if (withShadow) {
    ctx.shadowBlur = 0;
    ctx.shadowOffsetY = 0;
  }
}

/**
 * 绘制水平分隔线
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {number} x1 - 起点X
 * @param {number} y - Y坐标
 * @param {number} x2 - 终点X
 * @param {string} color - 线条颜色
 * @param {number} lineWidth - 线宽
 */
export function drawDivider(ctx, x1, y, x2, color = '#e5e5e7', lineWidth = 1) {
  ctx.strokeStyle = color;
  ctx.lineWidth = lineWidth;
  ctx.beginPath();
  ctx.moveTo(x1, y);
  ctx.lineTo(x2, y);
  ctx.stroke();
}

/**
 * 绘制常量监控面板
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {Array} constants - 常量数组
 * @param {Array} allPoints - 所有点的数组
 * @param {Object} layout - 布局对象
 * @param {string} title - 面板标题
 */
export function drawConstantsPanel(ctx, constants, allPoints, layout, title = '常量监控') {
  const panelX = 20;
  const panelY = 100;
  const panelWidth = layout.LEFT_PANEL_WIDTH - 40;
  const panelHeight = Math.max(140, 100 + constants.length * 35);
  
  // 绘制面板
  drawPanel(ctx, panelX, panelY, panelWidth, panelHeight);
  
  // 面板标题
  ctx.fillStyle = '#1d1d1f';
  ctx.font = '600 14px Times New Roman';
  ctx.fillText(title, panelX + 10, panelY + 25);
  
  // 分隔线
  drawDivider(ctx, panelX + 10, panelY + 35, panelX + panelWidth - 10);
  
  // 显示常量
  if (constants.length > 0) {
    ctx.font = 'italic 15px Times New Roman';
    constants.forEach((constant, index) => {
      const y = panelY + 65 + index * 35;
      const value = calculateConstantValue(constant, allPoints, constants);
      
      // 常量名称（面积常量使用下标格式）
      ctx.fillStyle = '#1d1d1f';
      let nameX = panelX + 10;
      
      if (constant.type === 'area') {
        // 绘制 S
        ctx.fillText('S', nameX, y);
        const sWidth = ctx.measureText('S').width;
        nameX += sWidth;
        
        // 绘制下标内容（转换数学符号）
        const content = constant.name.replace(/^[Ss]+/g, '');
        const mathContent = convertToMathSymbols(content);
        ctx.save();
        ctx.font = 'italic 10px Times New Roman'; // 下标字体更小
        ctx.fillText(mathContent, nameX, y + 3); // 向下偏移3像素形成下标效果
        const subscriptWidth = ctx.measureText(mathContent).width;
        ctx.restore();
        nameX += subscriptWidth;
        
        // 绘制等号
        ctx.fillText(' =', nameX, y);
      } else {
        // 非面积常量直接绘制（转换数学符号）
        const mathName = convertToMathSymbols(constant.name);
        ctx.fillText(mathName + ' =', nameX, y);
      }
      
      // 常量值
      ctx.fillStyle = '#007AFF';
      ctx.font = '600 16px Times New Roman';
      ctx.textAlign = 'right';
      ctx.fillText(value, panelX + panelWidth - 10, y);
      ctx.textAlign = 'left';
      ctx.font = 'italic 15px Times New Roman';
    });
  } else {
    // 无常量时显示提示
    ctx.fillStyle = '#999';
    ctx.font = '14px Times New Roman';
    ctx.textAlign = 'center';
    ctx.fillText('暂无监控常量', panelX + panelWidth / 2, panelY + 70);
    ctx.textAlign = 'left';
  }
}

/**
 * 绘制中间画布区域（带裁剪）
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {Object} layout - 布局对象
 * @returns {Object} 绘图区域信息 {x, y, width, height, innerX, innerY, innerWidth, innerHeight}
 */
export function drawCanvasArea(ctx, layout) {
  const areaX = layout.CANVAS_AREA_X;
  const areaY = layout.TITLE_HEIGHT + 10;
  const areaWidth = layout.CANVAS_AREA_WIDTH;
  const areaHeight = layout.height - layout.TITLE_HEIGHT - 70;
  
  // 外部白色面板
  ctx.fillStyle = '#ffffff';
  ctx.shadowColor = 'rgba(0,0,0,0.08)';
  ctx.shadowBlur = 15;
  ctx.shadowOffsetY = 3;
  ctx.beginPath();
  ctx.roundRect(areaX, areaY, areaWidth, areaHeight, 16);
  ctx.fill();
  ctx.shadowBlur = 0;
  ctx.shadowOffsetY = 0;
  
  // 内部灰色背景
  const innerX = areaX + 10;
  const innerY = areaY + 10;
  const innerWidth = areaWidth - 20;
  const innerHeight = areaHeight - 20;
  
  ctx.fillStyle = '#f8f9fa';
  ctx.beginPath();
  ctx.roundRect(innerX, innerY, innerWidth, innerHeight, 12);
  ctx.fill();
  
  return {
    x: areaX,
    y: areaY,
    width: areaWidth,
    height: areaHeight,
    innerX,
    innerY,
    innerWidth,
    innerHeight
  };
}

/**
 * 绘制提示框（"蓝点可拖动"）
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {Object} layout - 布局对象
 * @param {string} text - 提示文本
 * @param {number} hintWidth - 提示框宽度
 */
export function drawHint(ctx, layout, text = '蓝点可拖动', hintWidth = 200) {
  const hintY = layout.height - 45;
  const hintX = 20; // 左下角位置
  
  // 提示框背景
  ctx.fillStyle = '#f0f5ff';
  ctx.strokeStyle = '#007AFF';
  ctx.lineWidth = 1.5;
  ctx.beginPath();
  ctx.roundRect(hintX, hintY, hintWidth, 35, 18);
  ctx.fill();
  ctx.stroke();
  
  // 蓝点图标
  ctx.fillStyle = '#007AFF';
  ctx.beginPath();
  ctx.arc(hintX + 30, hintY + 17.5, 5, 0, Math.PI * 2);
  ctx.fill();
  
  // 提示文字
  ctx.fillStyle = '#007AFF';
  ctx.font = '600 14px Times New Roman';
  ctx.textAlign = 'left';
  ctx.fillText(text, hintX + 45, hintY + 22);
}

/**
 * 绘制按钮
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {Object} button - 按钮对象 {id, x, y, width, height, label}
 * @param {boolean} isHovered - 是否悬停
 */
export function drawButton(ctx, button, isHovered = false) {
  // 按钮背景（蓝色按钮）
  ctx.fillStyle = isHovered ? '#0051d5' : '#007AFF';
  
  ctx.shadowColor = 'rgba(0,0,0,0.08)';
  ctx.shadowBlur = isHovered ? 12 : 8;
  ctx.shadowOffsetY = isHovered ? 3 : 2;
  ctx.beginPath();
  const borderRadius = button.id === 'back' ? 17 : 12;
  ctx.roundRect(button.x, button.y, button.width, button.height, borderRadius);
  ctx.fill();
  ctx.shadowBlur = 0;
  ctx.shadowOffsetY = 0;
  
  // 按钮文字（白色）
  ctx.fillStyle = '#ffffff';
  const fontSize = button.id === 'back' ? '600 13px' : '600 15px';
  ctx.font = `${fontSize} Times New Roman`;
  ctx.textAlign = 'center';
  ctx.fillText(button.label, button.x + button.width / 2, button.y + button.height / 2 + 5);
  ctx.textAlign = 'left';
}

// ============ 交互工具函数 ============

/**
 * 创建标准按钮布局
 * @param {Object} layout - 布局对象
 * @returns {Array} 按钮数组
 */
export function createStandardButtons(layout) {
  const { width, height, RIGHT_PANEL_WIDTH } = layout;
  
  return [
    { 
      id: 'reset', 
      label: '重置', 
      x: width - RIGHT_PANEL_WIDTH + 20, 
      y: Math.max(100, height * 0.16), 
      width: Math.min(140, RIGHT_PANEL_WIDTH - 40), 
      height: Math.max(45, height * 0.08)
    },
    {
      id: 'back',
      label: '返回列表',
      x: width - 120,
      y: 18,
      width: 90,
      height: 34
    }
  ];
}

/**
 * 检查点击是否在按钮区域内
 * @param {number} x - 点击的X坐标
 * @param {number} y - 点击的Y坐标
 * @param {Object} button - 按钮对象 {x, y, width, height}
 * @returns {boolean} 是否点击在按钮内
 */
export function isPointInButton(x, y, button) {
  return x >= button.x && 
         x <= button.x + button.width &&
         y >= button.y && 
         y <= button.y + button.height;
}

/**
 * 查找被点击的按钮
 * @param {number} x - 点击的X坐标
 * @param {number} y - 点击的Y坐标
 * @param {Array} buttons - 按钮数组
 * @returns {Object|null} 被点击的按钮对象，如果没有则返回null
 */
export function findClickedButton(x, y, buttons) {
  for (const button of buttons) {
    if (isPointInButton(x, y, button)) {
      return button;
    }
  }
  return null;
}

/**
 * 计算几何图形的边界框
 * @param {Array} points - 点数组
 * @returns {Object} 边界框 {minX, minY, maxX, maxY, centerX, centerY, width, height}
 */
export function calculateBounds(points) {
  if (!points || points.length === 0) {
    return { minX: 0, minY: 0, maxX: 0, maxY: 0, centerX: 0, centerY: 0, width: 0, height: 0 };
  }
  
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
  
  points.forEach(point => {
    minX = Math.min(minX, point.x);
    minY = Math.min(minY, point.y);
    maxX = Math.max(maxX, point.x);
    maxY = Math.max(maxY, point.y);
  });
  
  const centerX = (minX + maxX) / 2;
  const centerY = (minY + maxY) / 2;
  const width = maxX - minX;
  const height = maxY - minY;
  
  return { minX, minY, maxX, maxY, centerX, centerY, width, height };
}

/**
 * 计算居中偏移量（使几何图形在画布区域居中）
 * @param {Array} points - 点数组
 * @param {Object} canvasArea - 画布区域信息 {innerX, innerY, innerWidth, innerHeight}
 * @returns {Object} 偏移量 {offsetX, offsetY}
 */
export function calculateCenterOffset(points, canvasArea) {
  if (!points || points.length === 0) {
    return { offsetX: 0, offsetY: 0 };
  }
  
  const bounds = calculateBounds(points);
  
  // 计算画布区域的中心点
  const canvasCenterX = canvasArea.innerX + canvasArea.innerWidth / 2;
  const canvasCenterY = canvasArea.innerY + canvasArea.innerHeight / 2;
  
  // 计算偏移量
  const offsetX = canvasCenterX - bounds.centerX;
  const offsetY = canvasCenterY - bounds.centerY;
  
  return { offsetX, offsetY };
}

// ============ React Hooks 工具函数 ============

/**
 * 设置响应式画布尺寸调整
 * @param {React.RefObject} containerRef - 容器引用
 * @param {Function} setCanvasSize - 设置画布尺寸的状态函数
 * @param {boolean} enabled - 是否启用（默认true）
 * @returns {Function} 清理函数
 */
export function setupResponsiveCanvas(containerRef, setCanvasSize, enabled = true) {
  if (!enabled) return () => {};
  
  const handleResize = () => {
    if (containerRef.current) {
      const size = getResponsiveCanvasSize();
      setCanvasSize(size);
    }
  };
  
  // 初始化
  handleResize();
  
  // 监听窗口大小变化和方向变化
  window.addEventListener('resize', handleResize);
  window.addEventListener('orientationchange', () => {
    setTimeout(handleResize, 100);
  });
  
  // 返回清理函数
  return () => {
    window.removeEventListener('resize', handleResize);
    window.removeEventListener('orientationchange', handleResize);
  };
}

/**
 * 设置高DPI画布并在数据变化时绘制
 * @param {React.RefObject} canvasRef - Canvas引用
 * @param {Object} canvasSize - 画布尺寸 {width, height}
 * @param {Function} drawFunction - 绘制函数
 */
export function setupCanvasDrawing(canvasRef, canvasSize, drawFunction) {
  if (!canvasRef.current) return;
  
  // 设置高DPI画布
  const ctx = setupHighDPICanvas(canvasRef.current, canvasSize.width, canvasSize.height);
  
  // 使用 requestAnimationFrame 优化绘制
  requestAnimationFrame(() => {
    drawFunction(ctx);
  });
}

/**
 * 根据常量名获取常量值（用于表达式计算）
 * @param {string} constantName - 常量名称
 * @param {Array} constants - 常量数组
 * @param {Array} allPoints - 所有点的数组
 * @returns {number|null} 常量值
 */
function getConstantValueByName(constantName, constants, allPoints) {
  const constant = constants?.find(c => c.name === constantName);
  if (!constant) return null;
  const val = calculateConstantValue(constant, allPoints, constants);
  const num = parseFloat(val);
  return isNaN(num) ? null : num;
}

/**
 * 计算单个等式的值（内部辅助函数）
 */
function evaluateEquationInternal(eq, getConstantValue) {
  if (eq.length === 0) return null;
  
  // 单个常量或数字
  if (eq.length === 1) {
    if (eq[0].type === 'constant') {
      return getConstantValue(eq[0].name);
    } else if (eq[0].type === 'number') {
      return eq[0].value;
    }
    return null;
  }
  
  // 构建tokens
  const tokens = eq.map(part => {
    if (part.type === 'constant') {
      return { type: 'value', value: getConstantValue(part.name) };
    } else if (part.type === 'number') {
      return { type: 'value', value: part.value };
    } else if (part.type === 'operator') {
      return { type: 'op', value: part.value };
    }
    return null;
  }).filter(t => t !== null);
  
  if (tokens.some(t => t.type === 'value' && (t.value === null || isNaN(t.value)))) {
    return null;
  }
  
  // 处理乘法、除法
  let i = 0;
  while (i < tokens.length) {
    if (i < tokens.length - 2 && tokens[i].type === 'value' && tokens[i + 1].type === 'op') {
      const op = tokens[i + 1].value;
      if (op === '*' || op === '/' || op === 'div') {
        const left = tokens[i].value;
        const right = tokens[i + 2].value;
        let result;
        if (op === '*') {
          result = left * right;
        } else {
          if (right === 0) return null;
          result = left / right;
        }
        tokens.splice(i, 3, { type: 'value', value: result });
      } else {
        i++;
      }
    } else {
      i++;
    }
  }
  
  // 处理加法和减法
  i = 0;
  while (i < tokens.length) {
    if (i < tokens.length - 2 && tokens[i].type === 'value' && tokens[i + 1].type === 'op') {
      const op = tokens[i + 1].value;
      if (op === '+' || op === '-') {
        const left = tokens[i].value;
        const right = tokens[i + 2].value;
        const result = op === '+' ? left + right : left - right;
        tokens.splice(i, 3, { type: 'value', value: result });
      } else {
        i++;
      }
    } else {
      i++;
    }
  }
  
  if (tokens.length === 1 && tokens[0].type === 'value') {
    return tokens[0].value;
  }
  
  return null;
}

/**
 * 计算表达式的值（用于Canvas）
 * @param {Object} expression - 表达式对象 {parts: []}
 * @param {Array} constants - 常量数组
 * @param {Array} allPoints - 所有点的数组
 * @returns {string|null} 格式化的值，如果表达式已完成则返回 null
 */
export function calculateExpressionValue(expression, constants, allPoints) {
  try {
    const { parts } = expression;
    if (parts.length === 0) return '-';
    
    // 检查最后一个部分的类型
    const lastPart = parts[parts.length - 1];
    const endsWithEqual = lastPart.type === 'operator' && lastPart.value === '=';
    
    // 如果以常量或数字结尾，表达式已完成，不需要显示计算结果
    if (!endsWithEqual) {
      return null;
    }
    
    // 如果以等号结尾，计算等号之前的值作为结果
    const equations = []; // 分割成多个等式
    let currentEq = [];
    
    // 按等号分割
    parts.forEach(part => {
      if (part.type === 'operator' && part.value === '=') {
        if (currentEq.length > 0) {
          equations.push(currentEq);
          currentEq = [];
        }
      } else {
        currentEq.push(part);
      }
    });
    if (currentEq.length > 0) {
      equations.push(currentEq);
    }
    
    // 创建 getConstantValue 函数
    const getConstantValue = (name) => {
      return getConstantValueByName(name, constants, allPoints);
    };
    
    // 最后一个等式为空（因为等号后面没有内容）
    const validResults = equations.slice(0, -1).map(eq => evaluateEquationInternal(eq, getConstantValue)).filter(r => r !== null);
    if (validResults.length === 0) return '-';
    // 取第一个值作为结果（会显示在等号后面）
    return validResults[0].toFixed(2);
  } catch {
    return '-';
  }
}

/**
 * 绘制表达式监控面板
 * @param {CanvasRenderingContext2D} ctx - Canvas上下文
 * @param {Array} expressions - 表达式数组
 * @param {Array} constants - 常量数组
 * @param {Array} allPoints - 所有点的数组
 * @param {Object} layout - 布局对象
 * @param {number} offsetY - Y轴偏移（在常量面板下方）
 */
export function drawExpressionsPanel(ctx, expressions, constants, allPoints, layout) {
  if (!expressions || expressions.length === 0) return;
  
  // 从左到右水平排列表达式
  const y = layout.height - 30; // 固定在底部
  let currentX = 280; // 从左侧开始，为蓝点提示预留空间（20px边距 + 200px提示框 + 20px间距）
  const expressionGap = 40; // 表达式之间的间距
  
  expressions.forEach((expression) => {
    
    // 计算表达式的值
    const value = calculateExpressionValue(expression, constants, allPoints);
    
    // 新的渲染逻辑：先识别所有分式，然后按顺序渲染
    const renderElements = [];
    const parts = expression.parts;
    let i = 0;
    
    while (i < parts.length) {
      const currentPart = parts[i];
      
      // 检查当前是否是 '/' 运算符（分式开始）
      if (currentPart.type === 'operator' && currentPart.value === '/') {
        // 这是一个分式，需要找到分子和分母
        // 分子：向前查找，包含所有 '*' 和 '/'，直到遇到 '+'、'-'、'=' 或开始
        // 分母：向后查找，包含所有 '*'，直到遇到 '+'、'-'、'='、'/' 或结束
        
        // 查找分子（向前）
        const numeratorParts = [];
        let numStart = i - 1;
        while (numStart >= 0) {
          const p = parts[numStart];
          // 遇到 '+'、'-'、'=' 时停止（这些运算符优先级低于 '/'）
          if (p.type === 'operator' && (p.value === '+' || p.value === '-' || p.value === '=')) {
            break;
          }
          numeratorParts.unshift(p); // 从前面插入，保持顺序
          numStart--;
        }
        
        // 查找分母（向后）
        const denominatorParts = [];
        let denomStart = i + 1;
        while (denomStart < parts.length) {
          const p = parts[denomStart];
          // 遇到 '+'、'-'、'='、'/' 时停止
          // 遇到 '/' 时停止，因为多个 '/' 应该被分别处理（如 A/B/C 应该是 (A/B)/C）
          if (p.type === 'operator' && (p.value === '+' || p.value === '-' || p.value === '=' || p.value === '/')) {
            break;
          }
          denominatorParts.push(p);
          denomStart++;
        }
        
        // 只有当分子和分母都有内容时才创建分式
        if (numeratorParts.length > 0 && denominatorParts.length > 0) {
          renderElements.push({ type: 'fraction', numerator: numeratorParts, denominator: denominatorParts });
          // 跳到分母后面
          i = denomStart;
          continue;
        }
      }
      
      // 普通元素（包括等号、'÷' 和 'div' 运算符等）
      renderElements.push(currentPart);
      i++;
    }
    
    // 辅助函数：测量parts的宽度
    const measureParts = (parts) => {
      let width = 0;
      parts.forEach(part => {
        if (part.type === 'constant') {
          const constant = constants?.find(c => c.name === part.name);
          if (constant && constant.type === 'area') {
            const content = part.name.replace(/^[Ss]+/g, '');
            const mathContent = convertToMathSymbols(content);
            ctx.font = 'italic 16px Times New Roman';
            width += ctx.measureText('S').width;
            ctx.font = 'italic 11px Times New Roman';
            width += ctx.measureText(mathContent).width;
            ctx.font = 'italic 16px Times New Roman';
          } else {
            const mathName = convertToMathSymbols(part.name);
            width += ctx.measureText(mathName).width;
          }
        } else if (part.type === 'number') {
          width += ctx.measureText(part.value.toString()).width;
        } else if (part.type === 'operator') {
          if (part.value === '^2') {
            // 平方符号使用小字体测量
            ctx.font = '12px Times New Roman';
            width += ctx.measureText('²').width;
            ctx.font = '16px Times New Roman';
          } else {
            const symbolMap = { '*': '×', 'div': '÷', '+': '+', '-': '−', '=': '=' };
            const symbol = symbolMap[part.value] || part.value;
            width += ctx.measureText(symbol).width;
          }
        }
      });
      return width;
    };
    
    // 辅助函数：绘制parts
    const drawParts = (parts, x, baseY) => {
      let currentX = x;
      let lastElementEndX = x; // 记录上一个元素的结束位置（用于平方符号）
      
      for (let i = 0; i < parts.length; i++) {
        const part = parts[i];
        
        // 检查是否是平方符号
        if (part.type === 'operator' && part.value === '^2') {
          // 平方符号：绘制在上一个元素的上方
          ctx.save();
          ctx.font = 'italic 12px Times New Roman';
          ctx.fillText('²', lastElementEndX, baseY - 6);
          const superscriptWidth = ctx.measureText('²').width;
          currentX = lastElementEndX + superscriptWidth;
          ctx.restore();
          ctx.font = 'italic 16px Times New Roman';
          continue;
        }
        
        if (part.type === 'constant') {
          const constant = constants?.find(c => c.name === part.name);
          if (constant && constant.type === 'area') {
            ctx.fillText('S', currentX, baseY);
            const sWidth = ctx.measureText('S').width;
            currentX += sWidth;
            
            const content = part.name.replace(/^[Ss]+/g, '');
            const mathContent = convertToMathSymbols(content);
            ctx.save();
            ctx.font = 'italic 11px Times New Roman';
            ctx.fillText(mathContent, currentX, baseY + 4);
            currentX += ctx.measureText(mathContent).width;
            ctx.restore();
            ctx.font = 'italic 16px Times New Roman';
          } else {
            const mathName = convertToMathSymbols(part.name);
            ctx.fillText(mathName, currentX, baseY);
            currentX += ctx.measureText(mathName).width;
          }
          lastElementEndX = currentX;
        } else if (part.type === 'number') {
          ctx.fillText(part.value.toString(), currentX, baseY);
          currentX += ctx.measureText(part.value.toString()).width;
          lastElementEndX = currentX;
        } else if (part.type === 'operator') {
          const symbolMap = { '*': '×', 'div': '÷', '+': '+', '-': '−', '=': '=' };
          const symbol = symbolMap[part.value] || part.value;
          ctx.fillText(symbol, currentX, baseY);
          currentX += ctx.measureText(symbol).width;
          // 运算符后不更新 lastElementEndX，因为平方符号应该跟在数字或常量后面
        }
      }
      return currentX;
    };
    
    // 从当前X位置开始绘制
    let formulaX = currentX;
    ctx.font = 'italic 16px Times New Roman';
    
    // 绘制表达式
    ctx.font = 'italic 16px Times New Roman';
    ctx.fillStyle = '#1d1d1f';
    
    renderElements.forEach(elem => {
      if (elem.type === 'fraction') {
        const numWidth = measureParts(elem.numerator);
        const denomWidth = measureParts(elem.denominator);
        const fractionWidth = Math.max(numWidth, denomWidth);
        
        // 绘制分子（居中）
        const numX = formulaX + (fractionWidth - numWidth) / 2;
        drawParts(elem.numerator, numX, y - 10);
        
        // 绘制分数线
        ctx.beginPath();
        ctx.moveTo(formulaX, y);
        ctx.lineTo(formulaX + fractionWidth, y);
        ctx.strokeStyle = '#1d1d1f';
        ctx.lineWidth = 1.5;
        ctx.stroke();
        
        // 绘制分母（居中）
        const denomX = formulaX + (fractionWidth - denomWidth) / 2;
        drawParts(elem.denominator, denomX, y + 15);
        
        formulaX += fractionWidth + 10;
      } else if (elem.type === 'constant') {
        const constant = constants?.find(c => c.name === elem.name);
        if (constant && constant.type === 'area') {
          ctx.fillText('S', formulaX, y);
          formulaX += ctx.measureText('S').width;
          
          const content = elem.name.replace(/^[Ss]+/g, '');
          const mathContent = convertToMathSymbols(content);
          ctx.save();
          ctx.font = 'italic 11px Times New Roman';
          ctx.fillText(mathContent, formulaX, y + 4);
          formulaX += ctx.measureText(mathContent).width;
          ctx.restore();
          ctx.font = 'italic 16px Times New Roman';
        } else {
          const mathName = convertToMathSymbols(elem.name);
          ctx.fillText(mathName, formulaX, y);
          formulaX += ctx.measureText(mathName).width;
        }
      } else if (elem.type === 'number') {
        ctx.fillText(elem.value.toString(), formulaX, y);
        formulaX += ctx.measureText(elem.value.toString()).width;
      } else if (elem.type === 'operator') {
        if (elem.value === '=') {
          ctx.fillText(' = ', formulaX, y);
          formulaX += ctx.measureText(' = ').width;
        } else {
          const symbolMap = { '*': '×', 'div': '÷', '+': '+', '-': '−' };
          const symbol = symbolMap[elem.value] || elem.value;
          ctx.fillText(symbol, formulaX, y);
          formulaX += ctx.measureText(symbol).width;
        }
      }
    });
    
    // 表达式值（如果有）
    if (value !== null) {
      ctx.fillStyle = '#34c759';
      ctx.font = '600 18px Times New Roman';
      ctx.fillText(value, formulaX, y);
      formulaX += ctx.measureText(value).width;
    }
    
    // 更新下一个表达式的起始X位置
    currentX = formulaX + expressionGap;
  });
}

